﻿/*************************************************************************************
 *
 * 文 件 名： Worker
 * 描    述： 台站、设备离线告警服务
 * 
 * 版    本： V1.0
 * 创 建 者： YSW
 * 创建时间： 2022-07-22 10:54:42
 * ======================================
 * 历史更新记录
 * 版本：V          修改时间：         修改人：
 * 修改内容：
 * ======================================
*************************************************************************************/

using CommonModel.Util;
using GXRTBTC.PublicClassLibrary.Util;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using RedisHelp;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GXRTBTC.PublicClassLibrary.Redis;
using GXRTBTC.PublicClassLibrary.Model;

namespace GXRTBTC.AlarmOfflineService
{
    /// <summary>
    /// 后台服务
    /// </summary>
    public class Worker : BackgroundService
    {
        #region 参数
        /// <summary>
        /// 当前节点名称
        /// 组成：节点所在IP+容器ID
        /// </summary>
        //public string nodeName { get; set; } = TransForm.GetContainerAddress() + "_" + Environment.MachineName;
        public string nodeName { get; set; } = TransForm.GetHostIPOrName() + "_" + Environment.MachineName;
        /// <summary>
        /// 日志
        /// </summary>
        private readonly ILogger mLogger;
        /// <summary>
        /// 配置
        /// </summary>
        private readonly IConfiguration mConfiguration;
        /// <summary>
        /// 锁对象
        /// </summary>
        private Object syncLock = new object();
        /// <summary>
        /// StackExchange.Redis
        /// </summary>
        private RedisHelper redisInit { get; set; }
        private string redisConnString { get; set; } = "";
#if DEBUG
        private int threadNum { get; set; } = 1;
#else
        private int threadNum { get; set; } = 2;
#endif
        /// <summary>
        /// 副本数，默认只有自身
        /// </summary>
        private int nodeNum { get; set; } = 1;
        /// <summary>
        /// 当前副本编号
        /// </summary>
        private int nodeNo { get; set; } = 0;
        /// <summary>
        /// 离线超时时间
        /// 默认30秒
        /// </summary>
        private int onlineTimeOut { get; set; } = 120;
        /// <summary>
        /// 轮询离线一次用时
        /// </summary>
        //private long stationLoopTime { get; set; } = -1;
        private long positionLoopTime { get; set; } = -1;
        /// <summary>
        /// 采集服务是否运行中
        /// </summary>
        private bool receiving { get; set; } = true;

        /// <summary>
        /// 台站信息缓存
        /// 台站编号为key
        /// </summary>
        //private Dictionary<string, Dictionary<string, string>> stationsInfo { get; set; } = new Dictionary<string, Dictionary<string, string>>();
        private List<string> positionIds { get; set; } = new List<string>();

        /// <summary>
        /// 配置集
        /// </summary>
        private Dictionary<string, string> paramInfo { get; set; } = new();
        #endregion

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="configuration"></param>
        public Worker(ILogger<Worker> logger, IConfiguration configuration)
        {
            this.mLogger = logger;
            this.mConfiguration = configuration;

            //给通用日志赋值
            CommonUtil.mLogger = logger;

            Init();
        }

        #region 初始化
        /// <summary>
        /// 初始化
        /// </summary>
        private void Init()
        {
            mLogger.LogInformation("初始化 。。。");

            try
            {
                //redis初始化
#if DEBUG
                redisConnString = RsaEncrypt.DecryptFree(mConfiguration["RedisConnStringDebug"]);
#else
                redisConnString = RsaEncrypt.DecryptFree(mConfiguration["RedisConnString"]);
#endif
                redisInit = new RedisHelper(redisConnString, mLogger);
            }
            catch { }

            //从缓存读取设置
            paramInfo = redisInit.HashGetAllDict<string>("paramInfo");
            string tmpOnlineTimeOut = paramInfo.GetValueOrDefault("OnlineTimeOut", "30");

            //超时时间
            //if (TransForm.IsInt(mConfiguration["onlineTimeOut"]))
            if (TransForm.IsInt(tmpOnlineTimeOut))
            {
                //onlineTimeOut = int.Parse(mConfiguration["onlineTimeOut"]);
                onlineTimeOut = int.Parse(tmpOnlineTimeOut);
            }

            //从redis读取缓存信息
            InitCacheInfo();

            mLogger.LogInformation("初始化完成");
        }

        /// <summary>
        /// redis缓存信息初始化
        /// </summary>
        private void InitCacheInfo()
        {
            try
            {
                //台站信息缓存
                //台站编号地址为key
                //Dictionary<string, Dictionary<string, string>> tmpStationsInfo = redisInit.StringGet<Dictionary<string, Dictionary<string, string>>>("stations");
                //stationsInfo = (tmpStationsInfo != null && tmpStationsInfo.Count > 0) ? tmpStationsInfo : stationsInfo;

                //redis缓存的所有工位号keys
                //List<string> tmpPositionIds = redisInit.HashKeys<string>("station_deviceInfos");
                List<string> tmpPositionIds = redisInit.StringGet<List<string>>("positionIds");
                positionIds = (tmpPositionIds != null && tmpPositionIds.Count > 0) ? tmpPositionIds : positionIds;
            }
            catch (Exception e)
            {
                mLogger.LogInformation("更新获取台站信息缓存失败，" + e);
            }
        }

        /// <summary>
        /// 初始化信息
        /// </summary>
        private void TimerInit()
        {
            Thread.Sleep(60 * 1000);
            while (true)
            {
                InitCacheInfo();
                Thread.Sleep(60 * 1000);
            }
        }
        #endregion

        #region 任务
        /// <summary>
        /// 数据处理
        /// 工位号
        /// </summary>
        private void DealDataPosition(int threadNo)
        {
            Thread.Sleep(1000);
            TimeSpan ts;
            RedisHelper redis = new RedisHelper(redisConnString, mLogger);
            IBatch batch = redis.CreateBatch();
            Dictionary<string, string> onlineStatus = new Dictionary<string, string>();
            int hourPre = 0;
            while (true)
            {
                //采集服务启动时，先写一条服务启动时间，本服务判断启动时间一分钟内不做离线判断，避免重启采集服务造成台站上报超时的告警
                //本条key存在时间为2分钟，2分钟内不做离线判断
                bool isStart = redis.KeyExists("DataReceiveService_start");
                //ping任务数量
                //int subTaskCount = redis.HashKeys<string>("SubTaskService").Count;
                if (!isStart)
                {
                    var sw = Stopwatch.StartNew();

                    try
                    {
                        //工位离线判断
                        if (positionIds != null && positionIds.Count > 0)
                        {
                            int orderAllNo = 0;
                            int orderMatchNo = 0;
                            foreach (string positionId in positionIds)
                            {
                                //多个副本分配任务
                                string onlineKey = "online_" + positionId;
                                if (orderAllNo % nodeNum == nodeNo)
                                {
                                    if (orderMatchNo % threadNum == threadNo)
                                    {
                                        //if (!onlineStatus.ContainsKey(onlineKey) || hourPre != DateTime.Now.Hour)
                                        if (true)
                                        {
                                            string online = redis.StringGet(onlineKey) ?? "1";
                                            if (onlineStatus.ContainsKey(onlineKey))
                                            {
                                                onlineStatus[onlineKey] = online;
                                            }
                                            else
                                            {
                                                onlineStatus.TryAdd(onlineKey, online);
                                            }
                                        }
                                        DateTime dt = redis.StringGet<DateTime>("newest_time_" + positionId);
                                        ts = DateTime.Now - dt;

                                        //总秒数超过参数给定的描述，则认为离线
                                        if (ts.TotalSeconds > (onlineTimeOut * 1.2) && !"1".Equals(onlineStatus[onlineKey]))
                                        {
                                            onlineStatus[onlineKey] = "1";
                                            batch.MStringSetAsync(onlineKey, "1");
                                        }

                                        //20240416 添加读取newest_time_和newest_time_cache_做比对，时间差超过一定时间，则记录差异总秒数，有空值则记录默认-1
                                        DateTime dtCache = redis.StringGet<DateTime>("newest_time_cache_" + positionId);
                                        if (dt != default(DateTime) && dtCache == default(DateTime))
                                        {
                                            //有空值则记录默认-1
                                            batch.MHashSetAsync("positionId_time_diff", positionId, -1);
                                        }
                                        else
                                        {
                                            //记录差异总秒数
                                            double d = Math.Abs((dt - dtCache).TotalSeconds);
                                            if (d > 0)
                                            { 
                                                batch.MHashSetAsync("positionId_time_diff", positionId, d);
                                            }
                                            else
                                            {
                                                batch.MHashDeleteAsync("positionId_time_diff", positionId);
                                            }
                                        }

                                        //20240416 添加读取newest_time_和newest_time_cache_stat_做比对，时间差超过一定时间，则记录差异总秒数，有空值则记录默认-1
                                        DateTime dtCacheStat = redis.StringGet<DateTime>("newest_time_cache_stat_" + positionId);
                                        if (dtCacheStat != default(DateTime))
                                        {
                                            double d = Math.Abs((dt - dtCacheStat).TotalSeconds);
                                            if (d > 0)
                                            {
                                                batch.MHashSetAsync("positionId_time_diff_stat", positionId, d);
                                            }
                                            else
                                            {
                                                batch.MHashDeleteAsync("positionId_time_diff_stat", positionId);
                                            }
                                        }
                                        else
                                        {
                                            batch.MHashDeleteAsync("positionId_time_diff_stat", positionId);
                                        }
                                    }
                                    orderMatchNo++;
                                }
                                else
                                {
                                    if (onlineStatus.ContainsKey(onlineKey))
                                    {
                                        onlineStatus.Remove(onlineKey);
                                    }
                                }
                                orderAllNo++;
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        mLogger.LogError("工位离线判断异常，" + e.Message);
                    }
                    finally
                    {
                        batch.Execute();
                    }

                    //统计单次轮询时间，用于打印日志到前台，便于观察
                    lock (syncLock)
                    {
                        positionLoopTime = positionLoopTime < sw.ElapsedMilliseconds ? sw.ElapsedMilliseconds : positionLoopTime;
                    }
                }

                hourPre = DateTime.Now.Hour;
                //间隔尽量少，判断也需要时间，实际单个台站离线判断也许就需要几秒了
                Thread.Sleep(500);
            }
        }
        #endregion

        #region 日志打印
        /// <summary>
        /// 打印日志
        /// </summary>
        private void TimerTask()
        {
            Thread.Sleep(3 * 1000);
            while (true)
            {
                string keySvr = "DataReceiveService";
                //获取key下所有节点及其时间
                Dictionary<string, DateTime> heartInfo = redisInit.HashGetAllDict<DateTime>(keySvr);
                int rcvCount = heartInfo.Count;
                //ping任务数量
                int subTaskCount = redisInit.HashKeys<string>("SubTaskService").Count;

                //loopOnceTime持续为0时，可能是因为采集服务重启中，2分钟内当前服务不判断离线
                mLogger.LogInformation(nodeName +
                    " 线程数=" + ThreadPool.ThreadCount +
                    " WS连接=" + WebsocketClientManager.websocketClient.GetCount() +
                    " 超时时间=" + onlineTimeOut + 
                    " 设备离线轮询时间=" + positionLoopTime + " 设备离线轮询=" + threadNum +
                    (positionLoopTime == 0 ? " 采集服务启动中" : "")
                    );

                lock (syncLock)
                {
                    //stationLoopTime = 0;
                    positionLoopTime = 0;
                }
                Thread.Sleep(10 * 1000);
            }
        }
        #endregion

        #region 心跳服务
        /// <summary>
        /// 心跳服务，定时往redis发送心跳数据
        /// 用于获取当前服务副本数量
        /// </summary>
        private void HeartBeatTask()
        {
            //当前节点ID 即UUID
            //string uuid = System.Guid.NewGuid().ToString("N");
            string uuid = nodeName;
            string keySvr = "AlarmOfflineService";   //心跳节点名称，保证唯一
            string lockKey = "lockKeyAlarmOfflineService";   //心跳节点锁key，保证唯一
            int gapSec = 2; //循环判断间隔
            int gapCheck = 20;   //判断时间差间隔，适当大于gapSec，如果大于，则判断为该节点离线，移除掉

            //每次启动清空
            Thread.Sleep(new Random().Next(1, (gapSec + 1)) * 1000);
            while (true)
            {
                try
                {
                    //更新自身节点最新时间
                    redisInit.HashSet(keySvr, uuid, DateTime.Now);

                    //判断副本数最新时间 锁过期时间为5秒
                    redisInit.LockAndDo(lockKey, uuid, TimeSpan.FromSeconds(5),
                        () =>
                        {
                            //获取key下所有节点及其时间
                            Dictionary<string, DateTime> heartInfo = redisInit.HashGetAllDict<DateTime>(keySvr);
                            if (heartInfo != null && heartInfo.Count > 0)
                            {
                                //判断节点是否超时gapCheck，超时则移除
                                //判断当前节点ID再心跳节点的位置
                                int idx = 0;
                                foreach (string node in heartInfo.Keys)
                                {
                                    if (uuid.Equals(node))
                                    {
                                        //节点ID位置
                                        nodeNo = idx;
                                    }

                                    //判断节点过期时间
                                    if ((DateTime.Now - heartInfo[node]).TotalSeconds > gapCheck)
                                    {
                                        redisInit.HashDelete(keySvr, node);
                                    }
                                    else
                                    {
                                        idx++;
                                    }
                                }

                                //获得节点数
                                nodeNum = idx;
                            }
                            return true;
                        });
                }
                finally
                {
                    Thread.Sleep(gapSec * 1000);
                }
            }
        }
        #endregion

        /// <summary>
        /// 启动任务
        /// </summary>
        /// <param name="stoppingToken"></param>
        /// <returns></returns>
        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            mLogger.LogInformation("服务已启动");

            //new Thread(DealDataStation).Start();   //数据处理线程 台站（离线、ping结果）
            //new Thread(DealDataPosition).Start();   //数据处理线程 工位号
                                                    //数据处理初始化
            for (int i = 0; i < threadNum; i++)
            {
                int threadNo = i;
                new Thread((ThreadStart)(delegate { DealDataPosition(threadNo); })).Start();
            }
            new Thread(HeartBeatTask).Start();   //当前服务心跳判断
            new Thread(TimerTask).Start();  //定时打印日志信息
            new Thread(TimerInit).Start();  //初始化信息
            return Task.CompletedTask;
        }
    }
}
